#include <cstdio>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <stdio.h>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include "napi/native_api.h"
#include "hilog/log.h"
#include "softbus_client_interface.h"
#include "discovery_service.h"
#include "softbus_bus_center.h"
#include "session.h"
#include "softbus_common.h"

using namespace std;

#define PACKAGE_NAME "hwpad_pkg"
#define LOCAL_SESSION_NAME "hwpad"
#define DEFAULT_PUBLISH_ID 123
#define DRONE_SESSION "gstreamer"
#define AI_SESSION "picture"
#define DEFAULT_CAPABILITY "osdCapability"
#define DEFAULT_SESSION_GROUP "group_test"

// Refresh the interval time of networking devices to avoid missing data acquisition
const int DISCOVER_SLEEP_TIME = 300;
// The first 100 bytes of pictures transmitted by other devices are the file saving path
const int PICTURE_DIR_LENGTH = 100;
// The length limit of the session name of the peer device
const int PEER_SESSION_NAME_LENGTH = 50;
// The maximum number of pictures saved to prevent the app core dump
const int MAX_PICTURE_SAVE_NUM = 60;

static callBack *callBackFunc;
int g_sessionId;
std::map<std::string, int> sessionNameAndId;
std::map<std::string, DeviceDetail> deviceIdAndInfo;

void clearOldPicture(const string &filePath);

/**
 * @brief Callback method after the service is published successfully, and print the publishing success log
 *
 * @param publishId Specify the ID of the service to distinguish other services
 */
void PublishSuccess(int publishId)
{
    OH_LOG_INFO(LOG_APP, "[INFO] <PublishSuccess>CB: publish %{public}d done", publishId);
}

/**
 * @brief Callback method after the service is published failed, and print the publishing fail log
 *
 * @param publishId  Specify the ID of the service to distinguish other services
 * @param reason The reason of publish failed
 */
void PublishFailed(int publishId, PublishFailReason reason)
{
    OH_LOG_ERROR(LOG_APP, "[ERROR] <PublishFailed>CB: publish %{public}d failed, reason=%{public}d", publishId, (int)reason);
}

/**
 * The callback function of the received message after the connection channel is established,
 * which is mainly used to save AI analysis result pictures and real-time video screenshots of UAVs.
 * And call the callback function of eTS to prompt that a new picture has been received
 *
 * @param sessionId Session ID established with other devices to distinguish different devices
 * @param data Data sent by the peer device
 * @param dataLength The size of the data sent by the peer device
 */
void OnReceiveCallback(int sessionId, const void *data, unsigned int dataLength)
{
    OH_LOG_INFO(LOG_APP, "[INFO] <OnReceiveCallback> The sessionId: %{public}d data is received and the callback function is started.", sessionId);
    char peerSessionName[PEER_SESSION_NAME_LENGTH];
    GetPeerSessionName(sessionId, peerSessionName, PEER_SESSION_NAME_LENGTH);
    string fileName = PICTURE_SAVE_PATH + (char *)data;
    OH_LOG_INFO(LOG_APP, "[INFO] <OnReceiveCallback> Current sessionName is: %{public}s, dataLength is: %{public}d",
                peerSessionName, dataLength);

    if (0 == strcmp(peerSessionName, DRONE_SESSION))
    {
        OH_LOG_INFO(LOG_APP, "[INFO] Uav-side callback message not processed!");
        return;
    }

    if (0 == strcmp(peerSessionName, AI_SESSION))
    {
        FILE *pictureFileStream;
        pictureFileStream = fopen(fileName.c_str(), "w+");
        fwrite((uint8_t *)data + PICTURE_DIR_LENGTH, dataLength - PICTURE_DIR_LENGTH, 1, pictureFileStream);
        fclose(pictureFileStream);
        OH_LOG_INFO(LOG_APP, "[INFO] <OnReceiveCallback> File: %{public}s writing succeeded", fileName.c_str());
        clearOldPicture(fileName);
        // Callback function of eTS
        callBackFunc(fileName);
        return;
    }
}

/**
 * @brief Interface for service publishing, specifying parameters and callback functions for service publishing
 *
 * @return int Return 0 if successful, otherwise return other values
 */
int PublishServiceInterface()
{
    OH_LOG_INFO(LOG_APP, "[INFO] Start to PublishService");
    PublishInfo info = {
        .publishId = DEFAULT_PUBLISH_ID,
        .mode = DISCOVER_MODE_PASSIVE,
        .medium = COAP,
        .freq = LOW,
        .capability = DEFAULT_CAPABILITY,
        .capabilityData = nullptr,
        .dataLen = 0,
    };
    IPublishCallback cb = {
        .OnPublishSuccess = PublishSuccess,
        .OnPublishFail = PublishFailed,
    };

    int hwpadPublicResult = PublishService(PACKAGE_NAME, &info, &cb);
    return hwpadPublicResult;
}

/**
 * @brief Interface to stop service publishing
 *
 */
void UnPublishServiceInterface()
{
    int ret;
    ret = UnPublishService(PACKAGE_NAME, DEFAULT_PUBLISH_ID);
    if (ret != 0)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] UnPublishService hwpad fail:%{public}d", ret);
    }
}

/**
 * @brief Interface for device discovery, printing and returning the discovered device information
 *
 * @param device Pointer to the discovered device
 */
void DeviceFound(const DeviceInfo *device)
{
    unsigned int i;
    OH_LOG_INFO(LOG_APP, "[INFO] <DeviceFound>CB: Device has found:");
    OH_LOG_INFO(LOG_APP, "\tdevId=%{public}s", device->devId);
    OH_LOG_INFO(LOG_APP, "\tdevName=%{public}s", device->devName);
    OH_LOG_INFO(LOG_APP, "\tdevType=%{public}d", device->devType);
    OH_LOG_INFO(LOG_APP, "\taddrNum=%{public}d", device->addrNum);
    for (i = 0; i < device->addrNum; i++)
    {
        OH_LOG_INFO(LOG_APP, "\taddr%{public}d:type=%{public}d,", i + 1, device->addr[i].type);
        switch (device->addr[i].type)
        {
        case CONNECTION_ADDR_WLAN:
        case CONNECTION_ADDR_ETH:
            OH_LOG_INFO(LOG_APP, "\tip=%{public}s,port=%{public}d,", device->addr[i].info.ip.ip, device->addr[i].info.ip.port);
            break;
        default:
            break;
        }
        OH_LOG_INFO(LOG_APP, "\tpeerUid=%{public}s", device->addr[i].peerUid);
    }
    OH_LOG_INFO(LOG_APP, "\tcapabilityBitmapNum=%{public}d", device->capabilityBitmapNum);
    for (i = 0; i < device->addrNum; i++)
    {
        OH_LOG_INFO(LOG_APP, "\tcapabilityBitmap[%{public}d]=0x%x", i + 1, device->capabilityBitmap[i]);
    }
    OH_LOG_INFO(LOG_APP, "\tcustData=%{public}s", device->custData);

    // Devices information for display
    DeviceDetail detail;
    detail.deviceName = device->devName;
    detail.deviceId = device->devId;
    detail.ipaddress = device->addr[0].info.ip.ip;
    detail.port = device->addr[0].info.ip.port;
    deviceIdAndInfo[device->devId] = detail;
}

/**
 * @brief Callback function for successful device detection
 *
 * @param subscribeId ID of the subscribed service, corresponding to the <publishId>
 */
void DiscoverySuccess(int subscribeId)
{
    OH_LOG_INFO(LOG_APP, "[INFO] <DiscoverySuccess>CB: discover subscribeId=%{public}d", subscribeId);
}

/**
 * @brief Callback function for device detection failure
 *
 * @param subscribeId ID of the subscribed service, corresponding to the <publishId>
 * @param reason Reason for probe failure
 */
void DiscoveryFailed(int subscribeId, DiscoveryFailReason reason)
{
    OH_LOG_ERROR(LOG_APP, "[ERROR] <DiscoveryFailed>CB: discover subscribeId=%{public}d failed, reason=%{public}d", subscribeId, (int)reason);
}

/**
 * @brief Device detection interface
 *
 * @return int Return 0 if successful, otherwise return other values
 */
int DiscoveryInterface()
{
    OH_LOG_INFO(LOG_APP, "[INFO] Start to Discovery");
    SubscribeInfo info = {
        .subscribeId = DEFAULT_PUBLISH_ID,
        .mode = DISCOVER_MODE_ACTIVE,
        .medium = COAP,
        .freq = LOW,
        .isSameAccount = false,
        .isWakeRemote = false,
        .capability = DEFAULT_CAPABILITY,
        .capabilityData = nullptr,
        .dataLen = 0,
    };
    IDiscoveryCallback cb = {
        .OnDeviceFound = DeviceFound,
        .OnDiscoverFailed = DiscoveryFailed,
        .OnDiscoverySuccess = DiscoverySuccess,
    };

    deviceIdAndInfo.clear();
    int hwpadDiscoveryResult = StartDiscovery(PACKAGE_NAME, &info, &cb);
    sleep(DISCOVER_SLEEP_TIME);
    return hwpadDiscoveryResult;
}

/**
 * @brief Interface for stopping device detection
 *
 */
void StopDiscoveryInterface()
{
    int ret;
    ret = StopDiscovery(PACKAGE_NAME, DEFAULT_PUBLISH_ID);
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] StopDiscovery fail:%{public}d", ret);
    }
}

/**
 * @brief The callback function established for Session
 * mainly records the relationship between sessionName and sessionId
 *
 * @param sessionId id of session
 * @param result 0 for success, otherwise other
 * @return int Return 0 if successful, otherwise return other values
 */
int SessionOpened(int sessionId, int result)
{
    if (result == 0)
    {
        g_sessionId = sessionId;
        char peerSessionName[50];
        GetPeerSessionName(sessionId, peerSessionName, 50);
        sessionNameAndId[peerSessionName] = sessionId;
        OH_LOG_INFO(LOG_APP, "[INFO] <SessionOpened>CB: session %{public}d open success, session name is: %{public}s", sessionId, peerSessionName);
    }
    else
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] <SessionOpened>CB: session %{public}d open failed, result is: %{public}d", sessionId, result);
    }

    return result;
}

/**
 * @brief Callback function for closing session channel
 *
 * @param sessionId id of session
 */
void SessionClosed(int sessionId)
{
    OH_LOG_INFO(LOG_APP, "[INFO] <SessionClosed>CB: session %{public}d closed", sessionId);
}

/**
 * @brief Callback function for receiving message
 *
 * @param sessionId id of session
 * @param data Data received
 * @param dataLen Length of data
 */
void MessageReceived(int sessionId, const void *data, unsigned int dataLen)
{
    OH_LOG_INFO(LOG_APP, "[INFO] <MessageReceived>CB: session %{public}d received %u bytes message=%{public}s", sessionId, dataLen, (const char *)data);
}

/**
 * @brief Create a Session Server Interface object
 *
 * @param func eTS callback function
 * @return int Return 0 if successful, otherwise return other values
 */
int CreateSessionServerInterface(callBack *func)
{
    OH_LOG_INFO(LOG_APP, "[INFO] Start to CreateSessionServer");
    callBackFunc = func;
    const ISessionListener sessionCB = {
        .OnSessionOpened = SessionOpened,
        .OnSessionClosed = SessionClosed,
        .OnBytesReceived = OnReceiveCallback,
        .OnMessageReceived = MessageReceived,
    };
    int hwpadSessionResult = CreateSessionServer(PACKAGE_NAME, LOCAL_SESSION_NAME, &sessionCB);

    return hwpadSessionResult;
}

/**
 * @brief Close the interface of the session service
 *
 */
void RemoveSessionServerInterface()
{
    int ret;
    ret = RemoveSessionServer(PACKAGE_NAME, LOCAL_SESSION_NAME);
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] RemoveSessionServer hwpad fail:%{public}d", ret);
    }
}

/**
 * @brief Open the interface of the session channel to establish a session and return the sessionId
 *
 * @param peerNetworkId The ID of the peer device, obtained from GetAllNodeDeviceInfoInterface
 * @param peerSessionName SessionName of the peer device, negotiated by both parties
 * @return int Return 0 if successful, otherwise return other values
 */
int OpenSessionInterface(const char *peerNetworkId, const char *peerSessionName)
{
    OH_LOG_INFO(LOG_APP, "[INFO] Start to open session peerNetworkId: %{public}s peerSessionName: %{public}s", peerNetworkId, peerSessionName);
    int timeout = 5;
    g_sessionId = -1;

    SessionAttribute attr = {
        .dataType = TYPE_BYTES,
        .linkTypeNum = 1,
        .linkType = {LINK_TYPE_WIFI_WLAN_2G},
        .attr = {RAW_STREAM},
    };

    int sessionId = OpenSession(LOCAL_SESSION_NAME, peerSessionName, peerNetworkId,
                                DEFAULT_SESSION_GROUP, &attr);
    if (sessionId < 0)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] OpenSessionInterface fail, ret=%{public}d", sessionId);
        return sessionId;
    }
    while (timeout)
    {
        if (g_sessionId == sessionId)
        {
            OH_LOG_INFO(LOG_APP, "[INFO] Obtaining the sessionId of device (sessionName:%{public}s, networkId: %{public}s), sessionId is: %{public}d",
                        peerSessionName, peerNetworkId, sessionId);
            return sessionId;
        }
        timeout--;
        sleep(1);
    }
    return -1;
}

/**
 * @brief session Close Interface
 *
 * @param sessionId id of session
 */
void CloseSessionInterface(int sessionId)
{
    CloseSession(sessionId);
}

/**
 * @brief Get the All Node Device Info Interface object
 *
 * @param dev Information of networking equipment
 * @return int The number of devices returned if the composition is successful, and - 1 if the networking fails
 */
int GetAllNodeDeviceInfoInterface(NodeBasicInfo **dev)
{
    int ret, num;

    ret = GetAllNodeDeviceInfo(PACKAGE_NAME, dev, &num);
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] GetAllNodeDeviceInfo by %{public}s fail:%{public}d", PACKAGE_NAME, ret);
        return -1;
    }

    OH_LOG_INFO(LOG_APP, "[INFO] <GetAllNodeDeviceInfo> by %{public}s return %{public}d Node", PACKAGE_NAME, num);
    return num;
}

/**
 * @brief Get the details of the devices in the network
 *
 * @param deviceDetail Structure of devices detail
 * @param dev Networking devices
 * @param devNum Num of  devices
 * @return int Number of devices getting information
 */
int GetDeviceDetailInterface(DeviceDetail *deviceDetail, NodeBasicInfo *dev, int devNum)
{
    char devId[UDID_BUF_LEN];
    int ret, count = 0;
    for (int i = 0; i < devNum; i++)
    {
        ret = GetNodeKeyInfo(PACKAGE_NAME, dev[i].networkId, NODE_KEY_UDID, (uint8_t *)devId, UDID_BUF_LEN);
        if (ret == 0)
        {
            deviceDetail[i] = deviceIdAndInfo[devId];
            deviceDetail[i].deviceName = dev[i].deviceName;
            deviceDetail[i].networkId = dev[i].networkId;
            OH_LOG_INFO(LOG_APP, "[INFO] Get device info: {deviceName: %{public}s, deviceId: %{public}s}",
                        deviceDetail[i].deviceName.c_str(), dev[i].networkId);
            count++;
        }
    }
    return count;
}

/**
 * @brief Release the interface of devices in the network
 *
 * @param dev Networking devices
 */
void FreeNodeInfoInterface(NodeBasicInfo *dev)
{
    FreeNodeInfo(dev);
}

/**
 * @brief Interface for sending data
 *
 * @param sessionId id of session
 * @param data data
 * @param len length of data
 * @return int Return 0 if successful, otherwise return other values
 */
int SendBytesInterface(int sessionId, const void *data, unsigned int len)
{
    int ret;
    ret = SendBytes(sessionId, data, len);
    if (ret)
    {
        OH_LOG_INFO(LOG_APP, "[INFO] SendBytes fail:%{public}d", ret);
    }
    return ret;
}

/**
 * @brief Delete old pictures according to the maximum number of pictures saved
 *
 * @param filePath Path of pictures
 */
void clearOldPicture(const string &filePath)
{
    int lastSlashIndex = filePath.find_last_of("/");
    string picturePath = filePath.substr(0, lastSlashIndex);
    vector<string> files;

    DIR *dirStream;
    dirStream = opendir(picturePath.c_str());
    struct dirent *dirInfo;
    while ((dirInfo = readdir(dirStream)) != 0)
    {
        if (dirInfo->d_type == DT_REG)
        {
            files.push_back((picturePath + "/" + dirInfo->d_name).c_str());
        }
    }
    closedir(dirStream);

    int fileCount = files.size();
    OH_LOG_INFO(LOG_APP, "[INFO] The number of pictures is: %{public}d", fileCount);
    sort(files.begin(), files.end());
    for (int i = 0; i < fileCount - MAX_PICTURE_SAVE_NUM; i++)
    {
        OH_LOG_INFO(LOG_APP, "[INFO] To delete the image: %{public}s", files[i].c_str());
        remove(files[i].c_str());
    }
}